package ezxpns.table;

import java.util.*;
import ezxpns.types.*;
import ezxpns.util.*;

public class Table implements Viewable<Table.View>
{
	private HashMap<String,Long> columnIDs;
	private HashMap<Long,Column<?>> columns;
	private long nextColumnID;
	private HashMap<Long,Record> records;
	private long nextRecordID;
	public Table()
	{
		columnIDs=new HashMap<String,Long>();
		columns=new HashMap<Long,Column<?>>();
		nextColumnID=0;
		records=new HashMap<Long,Record>();
		nextRecordID=0;
	}
	public boolean haveColumn(String name)
	{
		return columnIDs.containsKey(name);
	}
	private long getColumnID(String name) throws Exception
	{
		if( !columnIDs.containsKey(name) ) throw new Exception("Column \""+name+"\" does not exist!");
		return columnIDs.get(name);
	}
	public <TValue extends Value<TValue>> void addColumn(String name,Class<TValue> type,TValue value) throws Exception
	{
		if( columnIDs.containsKey(name) ) throw new Exception("Column \""+name+"\" already exists!");
		columnIDs.put(name,nextColumnID);
		columns.put(nextColumnID,new Column<TValue>(name,type,value.copy()));
		nextColumnID++;
	}
	public void removeColumn(String name) throws Exception
	{
		long columnID=getColumnID(name);
		columnIDs.remove(name);
		columns.remove(columnID);
	}
	public void renameColumn(String name,String name2) throws Exception
	{
		long columnID=getColumnID(name);
		columnIDs.remove(name);
		columnIDs.put(name2,columnID);
		columns.get(columnID).setName(name2);
	}
	private void editFields(Record record,HashMap<String,Value<?>> data) throws Exception
	{
		for( Map.Entry<String,Value<?>> field : data.entrySet() )
		{
			String name=field.getKey();
			Value<?> value=field.getValue();
			Column<?> column=columns.get(getColumnID(name));
			if( !column.getType().isInstance(value) )
			{
				throw new Exception("Record field value does not match type of column \""+name+"\"!");
			}
		}
		for( Map.Entry<String,Value<?>> field : data.entrySet() )
		{
			String name=field.getKey();
			Value<?> value=field.getValue();
			long columnID=getColumnID(name);
			record.setFieldValue(columnID,value);
		}
	}
	public boolean haveRecord(long id)
	{
		return records.containsKey(id);
	}
	private Record getRecord(long id) throws Exception
	{
		if( !records.containsKey(id) ) throw new Exception("Record "+id+" does not exist!");
		return records.get(id);
	}
	public long addRecord(HashMap<String,Value<?>> data) throws Exception
	{
		Record record=new Record(nextRecordID);
		editFields(record,data);
		records.put(nextRecordID,record);
		nextRecordID++;
		return nextRecordID-1;
	}
	public void removeRecord(long id) throws Exception
	{
		getRecord(id);
		records.remove(id);
	}
	public void editRecord(long id,HashMap<String,Value<?>> data) throws Exception
	{
		editFields(getRecord(id),data);
	}
	public Value<?> getFieldValue(long id,String name) throws Exception
	{
		Record record=getRecord(id);
		Long columnID=getColumnID(name);
		Column<?> column=columns.get(columnID);
		Value<?> value=record.getFieldValue(columnID);
		if( value==null ) return column.getValue();
		return value;
	}
	public HashMap<String,Value<?>> getRecordData(long id) throws Exception
	{
		Record record=getRecord(id);
		HashMap<String,Value<?>> data=new HashMap<String,Value<?>>();
		for( Map.Entry<String,Long> column : columnIDs.entrySet() )
		{
			data.put(column.getKey(),record.getFieldValue(column.getValue()));
		}
		return data;
	}
	@SuppressWarnings( "unchecked" )
	private <TValue extends Value<TValue>> int compare(Value<?> value1,Value<?> value2)
	{
		return ((TValue)value1).compareTo((TValue)value2);
	}
	public ArrayList<Long> filterRecords(HashMap<String,List<Bound<?>>> filter) throws Exception
	{
		ArrayList<Long> results=new ArrayList<Long>();
		for( Record record : records.values() )
		{
			boolean matchRecord=true;
			for( Map.Entry<String,List<Bound<?>>> columnFilter : filter.entrySet() )
			{
				boolean matchField=false;
				String name=columnFilter.getKey();
				long columnID=getColumnID(name);
				for( Bound<?> filterValue : columnFilter.getValue() )
				{
					Column<?> column=columns.get(columnID);
					if( !column.getType().isInstance(filterValue.lower()) )
					{
						throw new Exception("Filter field left bound does not match type of column \""+name+"\"!");
					}
					if( !column.getType().isInstance(filterValue.upper()) )
					{
						throw new Exception("Filter field right bound does not match type of column \""+name+"\"!");
					}
					Value<?> value=record.getFieldValue(columnID);
					if( value==null ) value=column.getValue();
					if( compare(filterValue.lower(),value)<=0 && compare(value,filterValue.upper())<=0 )
					{
						matchField=true;
						break;
					}
				}
				if( matchField==false )
				{
					matchRecord=false;
					break;
				}
			}
			if( matchRecord==true )
			{
				results.add(record.getID());
			}
		}
		return results;
	}
	public class View
	{
		public boolean haveColumn(String name)
		{ return Table.this.haveColumn(name); }
		public boolean haveRecord(long id)
		{ return Table.this.haveRecord(id); }
		public Value<?> getFieldValue(long id,String name) throws Exception
		{ return Table.this.getFieldValue(id,name); }
		public HashMap<String,Value<?>> getRecordData(long id) throws Exception
		{ return Table.this.getRecordData(id); }
		public ArrayList<Long> filterRecords(HashMap<String,List<Bound<?>>> filter) throws Exception
		{ return Table.this.filterRecords(filter); }
	}
	final private View view=new View();
	public View getView()
	{
		return view;
	}
}
